/*
 *  Broadcaster.c
 *  xGPS
 *
 *  Created by Miguel Arroz on 10/06/30.
 *  Copyright Miguel Arroz. All rights reserved.
 *
 */

#include "Broadcaster.h"
#include <arpa/inet.h>
#include <pthread.h>

#define PROTOCOL_VERSION 2
#define TIMER	0.10
#define DEFAULT_PORT 38714

XPLMDataRef latitudeRef;
XPLMDataRef longitudeRef;
XPLMDataRef headingRef;
int socketFd = -1;
struct sockaddr_storage address;
socklen_t addressLength;
long randomNumber = -1;
char buffer[1024];
pthread_mutex_t lock;
pthread_t pingerThread;
bool keepPinging;

void initBroadcaster() {
	keepPinging = true;
	
	int ret = pthread_mutex_init(&lock, NULL);
	if( ret != 0 ) {
		char buffer[1024];
		sprintf( buffer, "Error crating mutex: %d", ret );
		xGPSDebug( buffer );
	}
	
	ret = pthread_create( &pingerThread, NULL, startPingerThread, NULL );
	
	if( ret != 0 ) {
		char buffer[1024];
		sprintf( buffer, "Creation of the pinger thread failed with error %d.", ret );
		xGPSDebug( buffer );
	}
}

void stopBroadcaster( void ) {
	keepPinging = false;
	pthread_join( pingerThread, NULL );
}

void* startPingerThread( void *ptr ) {
	while( keepPinging ) {
		if( socketFd != -1 && randomNumber != -1 ) {
			int ret = pthread_mutex_lock( &lock );
		
			if( ret != 0 ) {
				char errorBuffer[1024];
				sprintf( errorBuffer, "Error locking mutex: %d", ret );
				xGPSDebug( errorBuffer );
			}
		
			sendAck();
		
			if( ret == 0 ) {
				ret = pthread_mutex_unlock( &lock );
				
				if( ret != 0 ) {
					char errorBuffer[1024];
					sprintf( errorBuffer, "Error unlocking mutex: %d", ret );
					xGPSDebug( errorBuffer );
				}
			}
		}
		
		sleep(1);
	}
	
	return NULL;
}

void startBroadcasting() {
	XPLMRegisterFlightLoopCallback( broadcastCallback, TIMER, NULL );    
}

void stopBroadcasting() {
	XPLMUnregisterFlightLoopCallback( broadcastCallback, NULL );
}

float broadcastCallback ( float inElapsedSinceLastCall, float inElapsedTimeSinceLastFlightLoop, int inCounter, void *inRefcon) {
	if( socketFd != -1 && randomNumber != -1 ) {
		xGPSData data = getCurrentData();
		
		int ret = pthread_mutex_lock( &lock );
		
		if( ret != 0 ) {
			char errorBuffer[1024];
			sprintf( errorBuffer, "Error locking mutex: %d", ret );
			xGPSDebug( errorBuffer );
		}
		
		serializeData( data );
		sendBufferToNetwork();
		
		
		if( ret == 0 ) {
			ret = pthread_mutex_unlock( &lock );
			
			if( ret != 0 ) {
				char errorBuffer[1024];
				sprintf( errorBuffer, "Error unlocking mutex: %d", ret );
				xGPSDebug( errorBuffer );
			}
		}
	}
	return TIMER;
}

void initDataRefs() {
	latitudeRef = XPLMFindDataRef( "sim/flightmodel/position/latitude" );
	longitudeRef = XPLMFindDataRef( "sim/flightmodel/position/longitude" );
	headingRef = XPLMFindDataRef( "sim/flightmodel/position/psi" );
}

void openSocket() {
	socketFd = socket( AF_INET6, SOCK_DGRAM, 0 );
	if( socket < 0 ) {
		xGPSDebug( "Could not open socket for broadcasting data" );
	}
}

xGPSData getCurrentData() {
	xGPSData data;
	
	data.latitude = XPLMGetDatad( latitudeRef );
	data.longitude = XPLMGetDatad( longitudeRef );
	data.heading = XPLMGetDataf( headingRef );
	
//	printData( data );
	
	return data;
}

void printData( xGPSData data ) {
	char buffer[1024];
	
	sprintf( buffer, "Latitude: %.10f, Longitude: %.10f, Heading: %.1f", data.latitude, data.longitude, data.heading );
	xGPSDebug( buffer );
}

void serializeData( xGPSData data ) {
	bzero( buffer, sizeof( buffer ) );
	sprintf( buffer, "S|%d|%li|DATA|%.10f|%.10f|%.1f|E", PROTOCOL_VERSION, randomNumber, data.latitude, data.longitude, data.heading );
}

void sendBufferToNetwork() {
	if( socketFd >= 0 ) {
		int bytes = sendto( socketFd, buffer, strlen( buffer ), 0, (struct sockaddr*) &address, addressLength );
		if( bytes == -1 ) {
			char errorBuffer[1024];
			sprintf( errorBuffer, "Error sending bytes: %d", errno );
			xGPSDebug( errorBuffer );
		}
	}
}

void sendAck() {
	bzero( buffer, sizeof( buffer ) );
	sprintf( buffer, "S|%d|%li|ACK|E", PROTOCOL_VERSION, randomNumber );
	sendBufferToNetwork();
}

void adjustSock() {
	struct sockaddr_in *addr;
	struct sockaddr_in6 *addr6;
	char buffer[1024];
	
	if( socketFd != -1 ) {
		close( socketFd );
		socketFd = -1;
	}
	
	switch( address.ss_family ) {
		case AF_INET:
			addr = (struct sockaddr_in *) &address;
			addr->sin_port = htons( DEFAULT_PORT );
			break;
		case AF_INET6:
			addr6 = (struct sockaddr_in6 *) &address;
			addr6->sin6_port = htons( DEFAULT_PORT );
			break;
		default:
			sprintf( buffer, "Unknown socket family: %d", address.ss_family );
			xGPSDebug( buffer );
			return;
	}
	
	openSocket();
}

void tuneBroadcaster( long randNum, struct sockaddr_storage addr, socklen_t addrLen ) {
	randomNumber = randNum;
	address = addr;
	addressLength = addrLen;
	
	int ret = pthread_mutex_lock( &lock );
	
	if( ret != 0 ) {
		char errorBuffer[1024];
		sprintf( errorBuffer, "Error locking mutex: %d", ret );
		xGPSDebug( errorBuffer );
	}
	
	adjustSock();
	sendAck();
	
	if( ret == 0 ) {
		ret = pthread_mutex_unlock( &lock );
		
		if( ret != 0 ) {
			char errorBuffer[1024];
			sprintf( errorBuffer, "Error unlocking mutex: %d", ret );
			xGPSDebug( errorBuffer );
		}
	}
}
